package com.sangmo.fts.common.authority;

import com.sangmo.boot.framework.common.session.UserSession;
import com.sangmo.boot.framework.common.utils.ApplicationUtils;
import com.sangmo.boot.framework.common.utils.BeanUtils;
import com.sangmo.boot.framework.data.entity.Entity;
import com.sangmo.boot.framework.data.filter.Criteria;
import com.sangmo.boot.framework.data.filter.PageResult;
import com.sangmo.boot.framework.data.filter.PageSearch;
import com.sangmo.boot.framework.data.repo.IRepo;
import com.sangmo.fts.common.authority.sql.INativeSupport;
import com.sangmo.fts.common.authority.sql.NativeSearchContext;
import org.apache.commons.lang3.StringUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.annotation.Annotation;
import java.util.Set;

public interface IAuthorityFilter<T extends Annotation> extends INativeSupport<T> {

    Class<T> getAnnotation();

    default boolean isUsedRight(T annotation, int expectedRight) {
        int anyRights = (int) BeanUtils.findAnnotationProperty(annotation, "anyRights", 0);
        return (anyRights & expectedRight) > 0;
    }

    default boolean isHaveAnyPerms(T annotation) {
        String anyPerms = (String)BeanUtils.findAnnotationProperty(annotation, "anyPerms", null);
        if (StringUtils.isEmpty(anyPerms)) {
            return true;
        }

        Set<String> perms = UserSession.Authorize.parsePermission(anyPerms);
        for (String perm : perms) {
            if (null != UserSession.findPermission(perm)) {
                return true;
            }
        }
        return false;
    }

    @Override
    default NativeSearchFilterResult doSearchFilter(NativeSearchContext context, T annotation, int expectedRight) {
        if (isHaveAnyPerms(annotation)) {
            if (isUsedRight(annotation, expectedRight)) {
                return internalNativeSearchFilter(context, annotation, expectedRight);
            }
        }
        return new NativeSearchFilterResult().setRejected();
    }

    NativeSearchFilterResult internalNativeSearchFilter(NativeSearchContext context, T annotation, int expectedRight);

    default SearchFilterResult doSearchFilter(EntitySearchContext context, T annotation, int expectedRight) {
        if (isHaveAnyPerms(annotation)) {
            if (isUsedRight(annotation, expectedRight)) {
                return internalSearchFilter(context, annotation, expectedRight);
            }
        }
        return new SearchFilterResult().setRejected(true);
    }

    SearchFilterResult internalSearchFilter(EntitySearchContext context, T annotation, int expectedRight);

    default boolean filter(EntityContext context, T annotation, int expectedRight) {
        if (isUsedRight(annotation, expectedRight)) {
            return internalFilter(context, annotation, expectedRight);
        }
        return false;
    }

    default boolean filterCreation(Entity entity, T annotation, int expectedRight) {
        if (isUsedRight(annotation, expectedRight)) {
            return internalFilterCreation(entity, annotation, expectedRight);
        }
        return false;
    }

    default boolean internalFilterCreation(Entity entity, T annotation, int expectedRight) {
        return true;
    }

    default boolean internalFilter(EntityContext context, T annotation, int expectedRight) {
        IRepo repo = ApplicationUtils.getBean(IRepo.class, context.getEntityClass());
        PageResult result = repo.search(new PageSearch(), new Criteria.CriteriaModifier() {
            @Override
            public Predicate onSearch(Root root, CriteriaBuilder cb, Predicate p) {
                Path path = root.get("id");
                Predicate ret =  cb.equal(path, context.getId());

                SearchFilterResult searchFilterResult = doSearchFilter(new EntitySearchContext(root, cb), annotation, expectedRight);

                if (searchFilterResult.isRejected()) {
                    ret = cb.equal(path, -1);
                } else {
                    if (null != searchFilterResult.predicate) {
                        ret = Criteria.concat(cb, ret, searchFilterResult.predicate, false);
                    }
                }
                return ret;
            }
        });
        return result.getRows().size() > 0;
    }

    class SearchFilterResult {
        boolean rejected;
        Predicate predicate;

        public boolean isRejected() {
            return rejected;
        }

        public SearchFilterResult setRejected(boolean rejected) {
            this.rejected = rejected;
            return this;
        }

        public Predicate getPredicate() {
            return predicate;
        }

        public SearchFilterResult setPredicate(Predicate predicate) {
            this.predicate = predicate;
            return this;
        }
    }
}
